package loggers

import (
	"bytes"
	"fmt"
	"github.com/sirupsen/logrus"
	"net/http"
	"os"
	"time"
	"todo-list/config"
	"todo-list/constants"
)

const formatTimeLayout = time.DateOnly

var (
	GinLogger = logrus.New()
)

type NormalLoggerFormatter struct {
	Prefix     string
	TimeFormat string
}

func (f NormalLoggerFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	// 设置颜色
	color := getColor(entry.Level)

	// 设置buffer缓冲区
	var b *bytes.Buffer
	if entry.Buffer == nil {
		b = &bytes.Buffer{}
	} else {
		b = entry.Buffer
	}
	// 文件的行号
	fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
	// 函数名
	// 设置格式
	fmt.Fprintf(b, "[\033[3%dm%s\033[0m]\033[3%dm[%s]\033[0m [%s] %s %s\n",
		constants.Cyan, f.Prefix,
		color, entry.Level,
		entry.Time.Format(f.TimeFormat),
		fileVal,
		entry.Message)
	return b.Bytes(), nil
}

type GinLoggerFormatter struct {
	Prefix     string
	TimeFormat string
}

func (f GinLoggerFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	return []byte(entry.Message), nil
}

type OutPutHook struct {
	file     *os.File
	logPath  string
	fileDate string
	level    logrus.Level
}

func (h *OutPutHook) Levels() []logrus.Level {
	switch h.level {
	case logrus.ErrorLevel:
		return []logrus.Level{logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel}
	default:
		return []logrus.Level{h.level}
	}
}

func (h *OutPutHook) Fire(entry *logrus.Entry) error {
	if entry.Time.Format(formatTimeLayout) != h.fileDate {
		h.fileDate = entry.Time.Format(formatTimeLayout)
		h.file.Close()
		h.createFile()
	}
	line, _ := entry.String()
	_, err := h.file.Write([]byte(line))
	if err != nil {
		return err
	}
	return nil
}

func (h *OutPutHook) createFile() {
	logPath := fmt.Sprintf("%s/%s", h.logPath, h.fileDate)
	_, err := os.Stat(logPath)
	if err != nil {
		err = os.MkdirAll(logPath, os.ModePerm)
		if err != nil {
			logrus.Errorln(err)
			return
		}
	}
	file, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, h.level.String()), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
	if err != nil {
		return
	}
	h.file = file
}

func GetOutPutHook(logRootPath string, level logrus.Level) *OutPutHook {
	fileDate := time.Now().Format(formatTimeLayout)
	fileHook := OutPutHook{logPath: logRootPath, fileDate: fileDate, level: level}
	fileHook.createFile()
	return &fileHook
}

func InitLog() {
	logrus.StandardLogger().SetFormatter(&NormalLoggerFormatter{"todo-list", time.DateTime})
	logrus.StandardLogger().SetReportCaller(true)
	LogConfig(logrus.StandardLogger())
	GinLogger.SetFormatter(&GinLoggerFormatter{})
	LogConfig(GinLogger)
}

func LogConfig(l *logrus.Logger) {
	l.SetLevel(logrus.DebugLevel)
	l.AddHook(GetOutPutHook(config.Configs.Log.GetString("root"), logrus.InfoLevel))
	l.AddHook(GetOutPutHook(config.Configs.Log.GetString("root"), logrus.WarnLevel))
	l.AddHook(GetOutPutHook(config.Configs.Log.GetString("root"), logrus.ErrorLevel))
}

func getColor(level logrus.Level) int {
	switch level {
	case logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel:
		return constants.Red
	case logrus.WarnLevel:
		return constants.Yellow
	case logrus.InfoLevel:
		return constants.Green
	case logrus.DebugLevel:
		return constants.Blue
	case logrus.TraceLevel:
		return constants.Gray
	default:
		return constants.Green
	}
}

func GinLogInfo(start time.Time, statusCode int, latency float64, clientIP, method, path, errorMsg string) {
	GinLogger.Infof("[GIN] %s |%s| %.2fms | %s |%s| %s \n %s",
		start.Format(time.DateTime),
		statusCodeColor(statusCode),
		latency,
		clientIP,
		methodColor(method),
		path,
		errorMsg,
	)
}

func statusCodeColor(code int) string {
	switch {
	case code >= http.StatusOK && code < http.StatusMultipleChoices:
		return fmt.Sprintf("\033[4%dm%d\033[0m", constants.Green, code)
	case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
		return fmt.Sprintf("%d", code)
	case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
		return fmt.Sprintf("\033[4%dm%d\033[0m", constants.Yellow, code)
	default:
		return fmt.Sprintf("\033[4%dm%d\033[0m", constants.Red, code)
	}
}

func methodColor(method string) string {
	switch method {
	case http.MethodGet:
		return fmt.Sprintf("\033[4%dm%s\033[0m", constants.Blue, method)
	case http.MethodPost:
		return fmt.Sprintf("\033[4%dm%s\033[0m", constants.Cyan, method)
	case http.MethodPut:
		return fmt.Sprintf("\033[4%dm%s\033[0m", constants.Yellow, method)
	case http.MethodDelete:
		return fmt.Sprintf("\033[4%dm%s\033[0m", constants.Red, method)
	case http.MethodPatch:
		return fmt.Sprintf("\033[4%dm%s\033[0m", constants.Green, method)
	case http.MethodHead:
		return fmt.Sprintf("\033[4%dm%s\033[0m", constants.Purple, method)
	default:
		return fmt.Sprintf("%s", method)
	}
}
